//
// Created by ddleed on 2018/12/5.
//

#include "iic.h"
#include "delay.h"

/**
 * IIC初始化
 */
void IIC_Init(void)
{
	GPIO_InitTypeDef GPIO_Initure;

	__HAL_RCC_GPIOH_CLK_ENABLE();   //使能GPIOH时钟

	//PH4,5初始化设置
	GPIO_Initure.Pin=GPIO_PIN_4|GPIO_PIN_5;
	GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出
	GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
	GPIO_Initure.Speed=GPIO_SPEED_FAST;     //快速
	HAL_GPIO_Init(GPIOH,&GPIO_Initure);

	SET_IIC_SCL(1);
	SET_IIC_SDA(1);
}

/**
 * 产生IIC起始信号
 * 开始信号：SCL为高电平时，SDA由高电平向低电平跳变，开始传送数据
 */
void IIC_Start(void)
{
	CMD_WRITE_SDA();

	//1. 总线空闲
	SET_IIC_SDA(1);
	SET_IIC_SCL(1);
	delay_us(4);

	//2. 拉低SDA线
	SET_IIC_SDA(0);
	delay_us(4);

	//3. 拉低SCL总线，准备发送或接收数据
	SET_IIC_SCL(0);

}

/**
 * 产生IIC停止信号
 * 结束信号：SCL为高电平时，SDA由低电平向高电平跳变，结束传送数据。
 */
void IIC_Stop(void)
{
	CMD_WRITE_SDA();

	//1. 拉低总线
	SET_IIC_SCL(0);
	SET_IIC_SDA(0);
    delay_us(4);

    //2. SDA上升沿
	SET_IIC_SCL(1);
	SET_IIC_SDA(1);
	delay_us(4);

}

/**
 * 等待应答信号到来
 * @return 			1，接收应答失败; 0，接收应答成功
 */
uint8_t IIC_Wait_Ack(void)
{
	uint8_t timeout = 0;
	CMD_READ_SDA();      //SDA设置为输入

	//1. 释放SDA线
	SET_IIC_SDA(1);
	delay_us(1);

	//2. 释放SCL线
	SET_IIC_SCL(1);
	delay_us(1);

	//3. 等待SDA线被拉低
	while(READ_IIC_SDA)
	{
		timeout++;
		if(timeout > 250)
		{
			IIC_Stop();
			return 1;
		}
	}

    //4. 拉低SCL总线，准备发送或接收数据
	SET_IIC_SCL(0);
	return 0;
}

/**
 * 产生ACK应答
 * 应答信号：接收数据的IC在接收到8bit数据后，
 * 向发送数据的IC发出特定的低电平脉冲，表示已收到数据。
 * CPU向受控单元发出一个信号后，等待受控单元发出一个应答信号，
 * CPU接收到应答信号后，根据实际情况作出是否继续传递信号的判断。
 * 若未收到应答信号，由判断为受控单元出现故障
 *
 * 对于反馈有效应答位ACK的要求是，
 * 接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低，
 * 并且确保在该时钟的高电平期间为稳定的低电平。
 */
void IIC_Ack(void)
{
    CMD_WRITE_SDA();

    //1. 接收器在第9个时钟脉冲之前的低电平期间
	SET_IIC_SCL(0);

	//2. 接收器将SDA线拉低
	SET_IIC_SDA(0);
	delay_us(2);

	//3. 确保在该时钟的高电平期间，SDA稳定为低电平
	SET_IIC_SCL(1);
	delay_us(2);

    //4. 拉低SCL总线，准备发送或接收数据
	SET_IIC_SCL(0);
}

/**
 * 不产生ACK应答
 */
void IIC_NAck(void)
{
    CMD_WRITE_SDA();

    //1. 接收器在第9个时钟脉冲之前的低电平期间
	SET_IIC_SCL(0);

    //2. 接收器不将SDA线拉低
	SET_IIC_SDA(1);
	delay_us(2);

    //3. 确保在该时钟的高电平期间，SDA稳定为高电平
	SET_IIC_SCL(1);
	delay_us(2);

    //4. 拉低SCL总线，准备发送或接收数据
	SET_IIC_SCL(0);
}


/**
 * IIC发送一个字节
 * @param tx_data
 * @note 注意此函数没有发送起始信号，不能直接使用
 *
 * I2C总线进行数据传送时，
 * 时钟信号为高电平期间，数据线上的数据必须保持稳定，
 * 只有在时钟线上的信号为低电平期间，数据线上的高电平或低电平状态才允许变化
 *
 * 即：数据在SCL的上升沿到来之前就需准备好。并在在下降沿到来之前必须稳定。
 */
void IIC_Send_Byte(uint8_t tx_data)
{
	uint8_t t;

	CMD_WRITE_SDA();

    //1. 拉低SCL总线，准备发送的数据
	SET_IIC_SCL(0);

	for(t=0; t<8; t++)
	{
	    //2. 准备数据
		SET_IIC_SDA((tx_data & 0x80U) >> 7U);
		tx_data <<= 1U;
		delay_us(2);

        //3. 拉高SCL总线，数据线保持稳定
		SET_IIC_SCL(1);
		delay_us(2);

        //1. 拉低SCL总线，准备发送的数据
		SET_IIC_SCL(0);
		delay_us(2);
	}
}

/**
 * 读1个字节
 * @param ack 	1,发送ACK;0,发送nACK
 * @return
 */
uint8_t IIC_Read_Byte(uint8_t ack)
{
	unsigned char i,receive=0;

	CMD_READ_SDA();//SDA设置为输入

	for(i=0; i<8; i++ )
	{
        //1. 拉低SCL总线，准备接收数据
		SET_IIC_SCL(0);
		delay_us(2);

        //2. 拉高SCL总线，数据线保持稳定
		SET_IIC_SCL(1);
		receive <<= 1U;

		//3. 接收数据
		if(READ_IIC_SDA)
			receive++;
		delay_us(1);
	}
	if (ack)
		IIC_Ack(); //发送ACK
	else
		IIC_NAck();//发送nACK

	return receive;
}

